home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 9 / CDACTUAL9.iso / share / Dos / VARIOS / pascal / SWAG9605.DDD / 0015_TP7 Terminal Demo.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-05-31  |  18.5 KB  |  596 lines

  1. program TERMINAL;
  2.  
  3. { ************************************************************************** }
  4. { TERMINAL                                                                   }
  5. { Turbo Pascal 7.0 Demo Programm                                             }
  6. { Written 1995 by Stephan A. Maciej                                          }
  7. { Internet: stephan@maciej.muc.de                                            }
  8. { For any questions, please mail to support@maciej.muc.de !                  }
  9. { WWW: http://www.muc.de/~stephanm                                           }
  10. { ************************************************************************** }
  11. { This program is for demonstration purpose only. Any commercial use with-   }
  12. { out the written permission of the author is illegal. Please report bugs,   }
  13. { corrections or any other ideas to stephan@maciej.muc.de. You are allowed   }
  14. { to distribute this program as often as you want as long as you do not      }
  15. { change it or edit it anyway. The author is not responsible for any damage  }
  16. { or destruction caused - directly or indirectly - by this program.          }
  17. { ************************************************************************** }
  18.  
  19. uses Crt;
  20.  
  21. const
  22.   TxDataReg = 0;                { transmitter data register            }
  23.   RxDataReg = 0;                { reciever data register               }
  24.   DivLow = 0;                   { divisor latch, low byte              }
  25.   DivHigh = 1;                  { divisor latch, high byte             }
  26.   IntrEnable = 1;               { interrupt enable register            }
  27.   IntrId = 2;                   { interrupt identification register    }
  28.   FifoCtrl = 2;                 { first-in/first-out buffer controller }
  29.   LineCtrl = 3;                 { line controll register               }
  30.   ModemCtrl = 4;                { modem controll register              }
  31.   LineStatus = 5;               { line status register                 }
  32.   ModemStatus = 6;              { modem status register                }
  33.   ScratchReg = 7;               { scratch pad (free useable)           }
  34.  
  35. const
  36.   QueueLen = 1024;              { Length of a queue in bytes }
  37.  
  38. type
  39.   { The TQueue type }
  40.   TQueue = record
  41.     Content: array[0..QueueLen - 1] of byte;
  42.     Start: word;
  43.     Stop: word;
  44.     end;
  45.  
  46. type
  47.   { The TQuadString type }
  48.   TQuadString = string[4];      { For the Hex-function }
  49.  
  50. var
  51.   { The two queues, one for incoming characters, the other for }
  52.   { outgoing characters.                                       }
  53.   InQueue: TQueue;              { The two queues: one for input buffering, }
  54.   OutQueue: TQueue;             { the other for output buffering           }
  55.  
  56. const
  57.   SpeedCount = 19;              { Number of valid speeds stored below }
  58.  
  59. const
  60.   AllowedSpeeds: array[0..SpeedCount] of longint =
  61.     (    50,      75,    110,    150,    300,    600,     900,    1200,
  62.        1800,    2400,   3600,   4800,   7200,   9600,   14400,   19200,
  63.       28800,   38400,  57600, 115200);
  64.  
  65.     { Just add any other speeds supported by your UART/Modem - don't }
  66.     { forget to increase the SpeedCount                              }
  67.  
  68. var
  69.   { Some global variables... }
  70.   PortNr: byte;                 { Number of the used port (1, 2 etc.)       }
  71.   PortBase: word;               { I/O base address of the used port         }
  72.   Speed: longint;               { Speed in baud                             }
  73.   UARTType: byte;               { UART type (one of the UART_xxxx constants }
  74.   UsedIRQ: byte;                { The number of the used IRQ                }
  75.  
  76. procedure SendEOI; assembler;
  77. { Send a EOI to the Interrupt Controller }
  78. asm
  79.         mov     al, 20h
  80.         out     20h, al
  81. end;
  82.  
  83. function GetPortBase(N: byte): word; assembler;
  84. { Read the I/O base address of the desired port from the BIOS data segment }
  85. asm
  86.         { Load the segment address of the BIOS data segment into ES }
  87.         mov     ax, 0040h
  88.         mov     es, ax
  89.         { Calculate the offset of the I/O port base address }
  90.         xor     ax, ax
  91.         mov     al, N
  92.         dec     al
  93.         shl     ax, 1
  94.         mov     si, ax
  95.         { Read the desired value and return }
  96.         mov     ax, es:[si]
  97. end;
  98.  
  99. procedure SetIntVec(N: byte; P: pointer); assembler;
  100. { Set an interrupt vector to the given address }
  101. asm
  102.         push    ds
  103.         { Just use the MS-DOS function 25h to set the vector }
  104.         mov     ah, 25h
  105.         mov     al, N
  106.         { ds:dx contains the vector to set }
  107.         lds     dx, P
  108.         int     21h
  109.         pop     ds
  110. end;
  111.  
  112. function GetIntVec(N: byte): pointer; assembler;
  113. { Get the interrupt vector }
  114. asm
  115.         push    es
  116.         { Use the MS-DOS function 35h to read the vector }
  117.         mov     ah, 35h
  118.         mov     al, N
  119.         int     21h
  120.         { Move the vector from es:bx to dx:ax }
  121.         mov     ax, bx
  122.         mov     dx, es
  123.         pop     es
  124. end;
  125.  
  126. procedure ResetQueue(var Q: TQueue);
  127. { Resets a Queue }
  128. begin
  129.   Q.Start := 0;
  130.   Q.Stop := 0;
  131. end;
  132.  
  133. procedure PutQueueByte(var Q: TQueue; B: byte);
  134. { Put a byte into the Queue }
  135. begin
  136.   { Put the byte into the Queue before incrementing the Queue end }
  137.   Q.Content[Q.Stop] := B;
  138.  
  139.   { Now increment the Queue end position. When Q.Stop reaches QueueLen, }
  140.   { be sure you don't increment Q.Stop but wrap it around to 0 again !  }
  141.   inc(Q.Stop);
  142.   if (Q.Stop = QueueLen) then
  143.     Q.Stop := 0;
  144.  
  145. end;
  146.  
  147. function GetQueueByte(var Q: TQueue; var B: byte): boolean;
  148. { Get a byte out of the Queue }
  149. begin
  150.   if (Q.Stop = Q.Start) then
  151.     { If the Queue is empty, just return false. Don't set B anyway. }
  152.     GetQueueByte := false
  153.   else
  154.     begin
  155.       { Queue is not empty: return true. }
  156.       GetQueueByte := true;
  157.  
  158.       { Get the first byte out of the Queue and return it in B. }
  159.       B := Q.Content[Q.Start];
  160.  
  161.       { Now increment the Queue position. Be sure to wrap it to zero }
  162.       { if the Q.Start field reaches the QueueLen constant.         }
  163.       inc(Q.Start);
  164.       if (Q.Start = QueueLen) then
  165.         Q.Start := 0;
  166.     end;
  167. end;
  168.  
  169. procedure SerialInterrupt; interrupt;
  170. { This procedure handles any incoming events from the UART. }
  171. var
  172.   Id: byte;
  173.   Trash: byte;
  174. begin
  175.   repeat
  176.     { Now read the Interrupt Identification register }
  177.     Id := Port[PortBase + IntrId];
  178.  
  179.     { Check if there's any pending interrupt. }
  180.     if ((Id and 1) = 0) then
  181.       begin
  182.  
  183.         { Now select the event. }
  184.         case ((Id and 6) shr 1) of
  185.           $03:
  186.             { The Line Status register changed. }
  187.             begin
  188.               { Just read the LSR to clear the event. }
  189.               Trash := Port[PortBase + LineStatus];
  190.             end;
  191.           $02:
  192.             { Data arrived at the UART. }
  193.             begin
  194.               { Read out the data from the RxD register and store }
  195.               { it in the incoming Queue.                         }
  196.               Trash := Port[PortBase + RxDataReg];
  197.               PutQueueByte(InQueue, Trash);
  198.             end;
  199.           $01:
  200.             { The TxD register is empty. }
  201.             begin
  202.               { If there's any byte in the outgoing Queue, send it to the }
  203.               { UART, else disable this interrupt.                        }
  204.               if (GetQueueByte(OutQueue, Trash)) then
  205.                 Port[PortBase + TxDataReg] := Trash
  206.               else
  207.                 Port[PortBase + IntrEnable] := $0D;
  208.  
  209.             end;
  210.           $00:
  211.             { The Modem Status register changed. }
  212.             begin
  213.               { Just read the MSR to clear the event. }
  214.               Trash := Port[PortBase + ModemStatus];
  215.             end;
  216.           end;
  217.         end;
  218.  
  219.     { Handle all interrupts ! Just check if there's }
  220.     { one more interrupt pending.                   }
  221.   until ((Id and 1) = 1);
  222.  
  223.   { Now tell the PIC our interrupt handler has finished it's work. }
  224.   SendEOI;
  225. end;
  226.  
  227. procedure SendChar(C: char);
  228. { Send a char to the modem }
  229. begin
  230.   { Put the character into the outgoing queue. }
  231.   PutQueueByte(OutQueue, byte(C));
  232.  
  233.   { Enable the "Transmitter register empty" interrupt }
  234.   Port[PortBase + IntrEnable] := $0F;
  235. end;
  236.  
  237. const
  238.   UART_Bad = 0;                 { Bad UART: not working or unidentifieable }
  239.   UART_8250 = 1;                { Standart 8250 UART                       }
  240.   UART_16450 = 2;               { 16450 UART (faster than 8250)            }
  241.   UART_16550 = 3;               { 16550 UART (with buggy 16-byte FIFO)     }
  242.   UART_16550A = 4;              { 16550A UART (with working FIFO)          }
  243.  
  244. function GetUARTType(Base: word): byte; assembler;
  245. { Check which UART type is assigned to the appropriate port }
  246. asm
  247.         { First difference: The 16450 has a scratch register which is }
  248.         { readable and writeable. Check if it's there. If not, we've  }
  249.         { got a 8250 UART.                                            }
  250.         mov     dx, Base
  251.         add     dx, ScratchReg
  252.         mov     al, 0AAh
  253.         out     dx, al
  254.         in      al, dx
  255.         cmp     al, 0AAh
  256.         je      @@1
  257.         mov     ax, UART_8250
  258.         jmp     @@5
  259. @@1:    { Now check out if the UART has got a FIFO. If it has none, it's  }
  260.         { a 16450, if it has one but it's not working it's a 16550.       }
  261.         { The UART will be identified as a 16550A if the FIFO is working. }
  262.         mov     dx, Base
  263.         add     dx, FifoCtrl
  264.         mov     al, 01h
  265.         out     dx, al
  266.         nop
  267.         mov     dx, Base
  268.         add     dx, IntrId
  269.         in      al, dx
  270.         and     al, 0C0h
  271.         cmp     al, 0C0h
  272.         jne     @@2
  273.         mov     al, UART_16550A
  274.         jmp     @@5
  275. @@2:    cmp     al, 80h
  276.         jne     @@3
  277.         mov     al, UART_16550
  278.         jmp     @@5
  279. @@3:    cmp     al, 0
  280.         jne     @@4
  281.         mov     al, UART_16450
  282.         jmp     @@5
  283. @@4:    mov     al, UART_Bad
  284. @@5:    nop
  285. end;
  286.  
  287. procedure UpCaseStr(var S: string); assembler;
  288. { Convert all chars in a string to uppercase letters }
  289. asm
  290.         les     di, S
  291.         xor     cx, cx
  292.         mov     cl, es:[di]
  293.         inc     di
  294. @@1:    mov     al, es:[di]
  295.         cmp     al, 'a'
  296.         jb      @@2
  297.         cmp     al, 'z'
  298.         ja      @@2
  299.         { Chars between 'a' and 'z' will be uppercased here. }
  300.         sub     al, 20h
  301. @@2:    mov     es:[di], al
  302.         inc     di
  303.         loop    @@1
  304. end;
  305.  
  306. function IsAllowedSpeed(Speed: longint): boolean;
  307. { Check if a speed is valid or not }
  308. var
  309.   I: byte;
  310. begin
  311.   { Return false by default. }
  312.   IsAllowedSpeed := false;
  313.  
  314.   { Check if you can find the desired speed in the speeds table. }
  315.   { If you found it, return "true".                              }
  316.   for I := 0 to SpeedCount do
  317.     if (AllowedSpeeds[I] = Speed) then
  318.       begin
  319.         IsAllowedSpeed := true;
  320.         exit;
  321.       end;
  322.  
  323.   { The default value ("false") will be returned if the speed wasn't }
  324.   { found in the table.                                              }
  325. end;
  326.  
  327. procedure GetCommandLine(var PortId: byte; var Speed: longint);
  328. { Check the command line and extract all parameters }
  329. var
  330.   S: string;
  331.   I: byte;
  332.   J: integer;
  333. begin
  334.   { If less than 1 parameter is specified, print a little help and }
  335.   { terminate the programm.                                        }
  336.   if (ParamCount <=0) THEN
  337.      BEGIN
  338.       writeln(' is one out of COM1, COM2, COM3 or COM4.');
  339.       writeln(' is the desired communication speed. The default value is 9600.');
  340.       writeln;
  341.  
  342.       { Halt the programm immediately. }
  343.       halt;
  344.     end;
  345.  
  346.   S := '';
  347.  
  348.   { Just create one long string from all parameters }
  349.   for I := 1 to ParamCount do
  350.     S := S + ParamStr(I);
  351.  
  352.   { Convert all lower-cased characters in that string to uppercased-chars }
  353.   UpCaseStr(S);
  354.  
  355.   { The first three bytes of the string must be 'COM'. If not, }
  356.   { there's an error in the command line.                      }
  357.   if (copy(S, 1, 3) <> 'COM') or (S[5] <> '/') then
  358.     begin
  359.       writeln('Error in command line. Call TERMINAL without options to see the help text.');
  360.       halt;
  361.     end;
  362.  
  363.   { Extract the number of the COM port that shall be used. }
  364.   I := byte(S[4]) - ord('0');
  365.  
  366.   { Check for it's boundaries ! }
  367.   if (i > 4) then
  368.     begin
  369.       writeln('Wrong COM port specified.');
  370.       halt;
  371.     end
  372.   else
  373.     PortId := I;
  374.  
  375.   { Now extract the desired speed and check if it's a valid input. }
  376.   val(copy(S, 6, length(S) - 5), Speed, J);
  377.   if (J > 0) then
  378.     begin
  379.       writeln('Error in speed specifier.');
  380.       halt;
  381.     end;
  382.  
  383.   { At last, check if the desired speed is supported. }
  384.   if (not IsAllowedSpeed(Speed)) then
  385.     begin
  386.       writeln('The speed you selected is not supported by this TERMINAL.');
  387.       halt;
  388.     end;
  389. end;
  390.  
  391. procedure EnableIRQ(IRQ: byte); assembler;
  392. { Enable a given IRQ from 0 to 7 }
  393. asm
  394.         mov     cl, IRQ
  395.         mov     bl, 1
  396.         shl     bl, cl
  397.         mov     ah, 255
  398.         sub     ah, bl
  399.         in      al, 21h
  400.         and     al, ah
  401.         out     21h, al
  402. end;
  403.  
  404. procedure DisableIRQ(IRQ: byte); assembler;
  405. { Disable a given IRQ from 0 to 7 }
  406. asm
  407.         mov     cl, IRQ
  408.         mov     bl, 1
  409.         shl     bl, cl
  410.         in      al, 21h
  411.         or      al, bl
  412.         out     21h, al
  413. end;
  414.  
  415. procedure PrintChar(Character: char); assembler;
  416. { Print a character on the screen - fast }
  417. asm
  418.         mov     ah, 0Eh
  419.         mov     al, Character
  420.         xor     bh, bh
  421.         mov     bl, 07h
  422.         int     10h
  423. end;
  424.  
  425. var
  426.   OldInterruptVec: pointer;
  427.  
  428. procedure SetupPort(PortBase: word; Speed: longint; IRQ: byte);
  429. { Setup the UART and prepare for communication. }
  430. var
  431.   D: word;
  432.   B: byte;
  433. begin
  434.   { For startup, disable the IRQ for the UART. }
  435.   DisableIRQ(IRQ);
  436.  
  437.   { Get the address of the old interrupt handler and set the vector }
  438.   { to our won interrupt handling procedure ("SerialInterrupt")     }
  439.   OldInterruptVec := GetIntVec($08 + IRQ);
  440.   SetIntVec($08 + IRQ, @SerialInterrupt);
  441.  
  442.   { Enable the "Recieved Data avaliable" interrupt so we can }
  443.   { read all data out of the UART's RxD register.            }
  444.   Port[PortBase + IntrEnable] := $01;
  445.  
  446.   { Now clear all pending interrupts - if any }
  447.   repeat
  448.     { Read all important registers to clear any interrupt types, }
  449.     { B is just used for temporary result storage                }
  450.     B := Port[PortBase + RxDataReg];
  451.     B := Port[PortBase + LineStatus];
  452.     B := Port[PortBase + ModemStatus];
  453.  
  454.     { Just repeat until no more interrupts are pending. }
  455.   until ((Port[PortBase + IntrId] and 1) = 1);
  456.  
  457.   { Enable the IRQ line for the UART after all pending interrupts }
  458.   { have been cleared.                                            }
  459.   EnableIRQ(IRQ);
  460.  
  461.   { Calculate the divisor latch contents for the desired baud rate }
  462.   D := (115200 div Speed);
  463.  
  464.   { Set the DLAB bit to 1, then write the divisor latch low and high bytes }
  465.   Port[PortBase + LineCtrl] := $80;
  466.   Port[PortBase + DivLow] := Lo(D);
  467.   Port[PortBase + DivHigh] := Hi(D);
  468.  
  469.   { Now set the divisor latch bit to 0 and write all other values }
  470.   Port[PortBase + LineCtrl] := $03;
  471.   Port[PortBase + ModemCtrl] := $0B;
  472.   Port[PortBase + IntrEnable] := $0F;
  473.  
  474.   { Check if a 16550A UART is present... }
  475.   if (UARTType = UART_16550A) then
  476.     begin
  477.       { Clear the FIFO queues }
  478.       Port[PortBase + FifoCtrl] := $07;
  479.  
  480.       { Enable the FIFO queues }
  481.       Port[PortBase + FifoCtrl] := $C1;
  482.  
  483.       { Print a message so the user recognizes the FIFO queues are on }
  484.       writeln('Enabling 16550A FIFO queues...');
  485.     end;
  486. end;
  487.  
  488. procedure RunTerminal(PortBase: word);
  489. { Run the Terminal }
  490. var
  491.   B: char;
  492. begin
  493.   { Just print a free line }
  494.   Writeln;
  495.  
  496.   repeat
  497.     { Was a key pressed ? If yes, the character is written }
  498.     { into the outgoing queue.                             }
  499.     if (KeyPressed) then
  500.       begin
  501.         { Read the key. If it was , the terminal session will }
  502.         { be aborted, else the character will be sent out.         }
  503.         B := ReadKey;
  504.         if (B = #27) then
  505.           SendChar(B);
  506.       end
  507.     { Is there any character in the incoming queue ? }
  508.     else if (GetQueueByte(InQueue, byte(B))) then
  509.       { If yes, just get the character and write it onto screen. }
  510.       PrintChar(B);
  511.  
  512.     { Repeat this loop until the  key was pressed. }
  513.   until (B = #27);
  514. end;
  515.  
  516. procedure ResetPort(PortBase: word; IRQ: byte);
  517. { Reset UART and reset interrupt vectors }
  518. begin
  519.   { Reset the DLAB bit. Clear all other registers. }
  520.   Port[PortBase + LineCtrl] := Port[PortBase + LineCtrl] and $7F;
  521.   Port[PortBase + ModemCtrl] := 0;
  522.   Port[PortBase + IntrEnable] := 0;
  523.  
  524.   { Disable the IRQ for the UART, then restore the old interrupt vector. }
  525.   DisableIRQ(IRQ);
  526.   SetIntVec($08 + IRQ, OldInterruptVec);
  527.  
  528.   { Inform the user that the terminal session was aborted. }
  529.   Writeln;
  530.   Writeln('Port closed.');
  531. end;
  532.  
  533. function Hex(W: word): TQuadString;
  534. { Convert a number into hexadecimal outfit }
  535. const
  536.   HexChars: array[0..15] of char = '0123456789ABCDEF';
  537. begin
  538.   Hex := HexChars[W shr 12] +
  539.          HexChars[(W shr 8) and 15] +
  540.          HexChars[(W shr 4) and 15] +
  541.          HexChars[W and 15];
  542. end;
  543.  
  544. procedure WritePortInfo;
  545. { Output some info about the selected port }
  546. begin
  547.   write('Using COM', PortNr, ' (base address ', Hex(PortBase), ', IRQ ', UsedIRQ);
  548.   write(', UART is a ');
  549.  
  550.   { Print the detected UART type }
  551.   case UARTType of
  552.     UART_8250:
  553.       write('8250');
  554.     UART_16450:
  555.       write('16450');
  556.     UART_16550:
  557.       write('16550');
  558.     UART_16550A:
  559.       write('16550A');
  560.     end;
  561.  
  562.   writeln(')...');
  563. end;
  564.  
  565. {--- Main Routine ---}
  566. begin
  567.   { Just output some information. }
  568.   writeln;
  569.   writeln('TERMINAL     Version 1.00     Written 1995 by Stephan A. Maciej');
  570.   writeln('Internet: stephan@maciej.muc.de     http://www.muc.de/~stephanm');
  571.   writeln;
  572.  
  573.   { Check for some correct parameters on the command line. }
  574.   GetCommandLine(PortNr, Speed);
  575.   PortBase := GetPortBase(PortNr);
  576.   if (PortBase = 0) then
  577.     begin
  578.       writeln('COM', PortNr, ': no such port !');
  579.       halt;
  580.     end;
  581.   UsedIRQ := 4 - ((PortNr - 1) and 1);
  582.   UARTType := GetUARTType(PortBase);
  583.  
  584.   { Reset both the incoming as well as the outgoing Queue. }
  585.   ResetQueue(InQueue);
  586.   ResetQueue(OutQueue);
  587.  
  588.   { Write some information about the selected port. }
  589.   WritePortInfo;
  590.  
  591.   { Setup the port, run the terminal and reset the port when ready. }
  592.   SetupPort(PortBase, Speed, UsedIRQ);
  593.   RunTerminal(PortBase);
  594.   ResetPort(PortBase, UsedIRQ);
  595. end.
  596.